#include "sched_lottery.h"
#include <cstdlib>
#include <iostream>

using namespace std;

/**
  @Ejercicio 5.
  @Purpose: Implementar al menos la idea básica del algoritmo y la optimización de tickets compensatorios 
	    (compensation tickets)
 @Parameters:
      core    : cantidad de cores 
      quantum : ticks de cpu que puede utilizar una tarea
      semilla : semilla de la secuencia pseudoaleatoria 
  @return: void
*/
SchedLottery::SchedLottery(vector<int> argn) {
  // Lottery solo funciona con un core
  // cout << "Cores:    " << argn[0] << endl;
  // cout << "Quantum:  " << argn[1] << endl;
  // cout << "Seed:     " << argn[2] << endl;  
  this->quantum = argn[1];
  this->remainder = argn[1];
  this->tasksInStandBy.clear();
  srand(argn[2]);  
}

SchedLottery::~SchedLottery() {
}

void SchedLottery::load(int pid,int deadline) {
  
  task_lottery task;
  task.pid = pid;
  task.tickets = 1;
  task.block = false;
  this->tasksInStandBy.push_back(task);
  
}

void SchedLottery::load(int pid) {
  load(pid, 0);
}

void SchedLottery::setBlock(int pid2block, bool block) {
  vector< task_lottery >::iterator it;  
  for (it = this->tasksInStandBy.begin() ; it != this->tasksInStandBy.end() ; ++it) {
    if ((*it).pid == pid2block) {
	(*it).block = block;      
    }
  }
}

void SchedLottery::block(int pid) {  
  setBlock(pid, true);
}

void SchedLottery::unblock(int pid) {
  setBlock(pid, false);
}

void SchedLottery::removeTask(int remove_pid) {
  vector< task_lottery >::iterator it;  
  for (it = this->tasksInStandBy.begin() ; it != this->tasksInStandBy.end() ; ++it) {
      if ((*it).pid == remove_pid ) {
	this->tasksInStandBy.erase(it);
	break;
      }
  }
}

void SchedLottery::doCompensation(int compensation_pid) {
  vector< task_lottery>::iterator it; 
  for (it = this->tasksInStandBy.begin() ; it != this->tasksInStandBy.end() ; ++it) {
      if ((*it).pid == compensation_pid) {
			//cout << "Compensation pid: " << (*it).pid << " current_pid: " << current_pid(0) << " tickets: " << (*it).tickets;
			//(*it).tickets += this->remainder;
			//cout << " tickets after: " << (*it).tickets << endl;
			(*it).tickets += this->quantum / (this->quantum - this->remainder);
			break;
      }
  } 
}

/** 
 * 
 * scheduler draws a random ticket to select the next process
 * 
 */
int SchedLottery::choseByDrawLottery(int cpu) {
    int total_tickets = 0;
    int task_chosen = IDLE_TASK;
    vector < task_lottery >::iterator it;
    
    for (it = this->tasksInStandBy.begin() ; it != this->tasksInStandBy.end(); it++) {
      if ((*it).pid != current_pid(cpu) && !(*it).block) {
	  total_tickets += (*it).tickets;	  
      }
    }
    
    //cout << "Total tickets " << total_tickets << endl;
    if ((total_tickets) == 0)
	return task_chosen;    
    
    int winner = rand()%(total_tickets);
    int summation = 0;
   // cout << "Total tickets: " << total_tickets << " winner: " << winner;
    for (it = this->tasksInStandBy.begin() ; it != this->tasksInStandBy.end(); it++) {
      if ((*it).pid != current_pid(cpu) && !(*it).block) {
	summation += (*it).tickets;
	if (summation > winner) {
	   // found the winner	   
	   task_chosen = (*it).pid;
	//   cout << " pid: " << (*it).pid << " tickets " << (*it).tickets << " acumulated: " << summation << endl;
	   break;
	}
      }      
    }
    return task_chosen;	
}

int SchedLottery::nextTask(int cpu) {
  this->remainder = this->quantum;
  return choseByDrawLottery(cpu);
}

int SchedLottery::tick(int cpu, const enum Motivo m) {
  int taskToExecute = IDLE_TASK;
  switch (m) {
    // EXIT: la tarea terminó (ejecution return)
    case EXIT:        
		removeTask(current_pid(cpu));
		taskToExecute = nextTask(cpu);
		break;
    // TICK: la tarea consumió todo el ciclo utilizando el CPU
    case TICK:
		this->remainder--;
		// Si se terminó el quantum
		if (this->remainder == 0) {
		  // elijo otra tarea
		  taskToExecute = nextTask(cpu);	  
		} else {	  
		  taskToExecute = current_pid(cpu);
		}
		break;
    // BLOCK: la tarea ejecutó una llamada bloqueante o permaneció bloqueada durante el último ciclo.
    case BLOCK:	
		this->remainder--;
		doCompensation(current_pid(cpu));
		block(current_pid(cpu));
		taskToExecute = nextTask(cpu);
		break;
  }
  return taskToExecute;
}
